package com.platform.modules.common.controller;

import cn.hutool.core.date.DateUtil;
import cn.hutool.extra.qrcode.QrCodeUtil;
import cn.hutool.extra.qrcode.QrConfig;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.platform.common.aspectj.IgnoreAuth;
import com.platform.common.config.WxPayConfig;
import com.platform.common.constant.AppConstants;
import com.platform.common.shiro.ShiroUtils;
import com.platform.common.shiro.vo.LoginUser;
import com.platform.common.utils.pay.*;
import com.platform.common.utils.redis.RedisUtils;
import com.platform.common.web.domain.AjaxResult;
import com.platform.modules.chat.domain.ChatPay;
import com.platform.modules.chat.domain.ChatUser;
import com.platform.modules.chat.service.ChatPayService;
import com.platform.modules.chat.service.ChatUserService;
import com.platform.modules.chat.utils.UUIDgen;
import com.platform.modules.common.utils.NonceUtil;
import com.platform.modules.common.vo.WxpayVo;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager;
import com.wechat.pay.contrib.apache.httpclient.constant.WechatPayHttpHeaders;
import com.wechat.pay.contrib.apache.httpclient.notification.Notification;
import com.wechat.pay.contrib.apache.httpclient.notification.NotificationHandler;
import com.wechat.pay.contrib.apache.httpclient.notification.NotificationRequest;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.jdom2.JDOMException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.TimeUnit;

@RestController
@Slf4j
@RequestMapping(value = "/wxpay")
public class WxpayController extends PayBaseController {
    @Resource
    private RedisUtils redisUtils;
    @Resource
    private ChatUserService chatUserService;
    @Autowired
    private ChatPayService chatPayService;
    private static CloseableHttpClient httpClient;
    private static CertificatesManager certificatesManager;
    private static Verifier verifier;

    @GetMapping("/pay/{fee}")
    public AjaxResult wxPayFunction(@PathVariable String fee) throws Exception{
        PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(WxPayConfig.privateKeyForDecrypt);
        // 获取证书管理器实例
        certificatesManager = CertificatesManager.getInstance();
        // 向证书管理器增加需要自动更新平台证书的商户信息
        certificatesManager.putMerchant(WxPayConfig.mchid, new WechatPay2Credentials(WxPayConfig.mchid,
                        new PrivateKeySigner(WxPayConfig.mchSerialNo, merchantPrivateKey)),
                WxPayConfig.apiV3Key.getBytes(StandardCharsets.UTF_8));
        // 从证书管理器中获取verifier
        verifier = certificatesManager.getVerifier(WxPayConfig.mchid);
        httpClient = WechatPayHttpClientBuilder.create()
                .withMerchant(WxPayConfig.mchid, WxPayConfig.mchSerialNo, merchantPrivateKey)
                .withValidator(new WechatPay2Validator(certificatesManager.getVerifier(WxPayConfig.mchid)))
                .build();
        //请求URL
        HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi");
        String tradeNo = UUIDgen.getUUID(20);
        LoginUser payUser = ShiroUtils.getLoginUser();
        // 请求body参数
        String reqdata = "{"
                + "\"amount\": {"
                + "\"total\": "+fee+","
                + "\"currency\": \"CNY\""
                + "},"
                + "\"mchid\": \""+WxPayConfig.mchid+"\","
                + "\"description\": \"卡费\","
                + "\"notify_url\": \""+WxPayConfig.notify_url+"\","
                + "\"payer\": {"
                + "\"openid\": \""+payUser.getOpenId()+"\"" + "},"
                + "\"out_trade_no\": \""+tradeNo+"\","
                + "\"goods_tag\": \"OCRG\","
                + "\"appid\": \"wxae0f0d0aef1a8645\"" + "}";
        StringEntity entity = new StringEntity(reqdata,"utf-8");
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");
        //完成签名并执行请求
        CloseableHttpResponse response = httpClient.execute(httpPost);
        try {
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200) {
                System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));
                String jsonStr = EntityUtils.toString(response.getEntity());
                JSONObject json = JSON.parseObject(jsonStr);
                String prepay_id = json.getString("prepay_id");
                String nonceStr = NonceUtil.createNonce(32);
                long timestamp = Instant.now().getEpochSecond();
                String paySign = this.getPaySign("wxae0f0d0aef1a8645", timestamp, nonceStr, prepay_id);
                JSONObject jsonRtn = new JSONObject();
                jsonRtn.put("appId", "wxae0f0d0aef1a8645");
                jsonRtn.put("nonceStr", nonceStr);
                jsonRtn.put("package", "prepay_id="+prepay_id);
                jsonRtn.put("signType", "RSA");
                jsonRtn.put("timeStamp", timestamp);
                jsonRtn.put("paySign", paySign);
                redisUtils.set(payUser.getUserId()+"pay", tradeNo, AppConstants.REDIS_MP_WEATHER_TIME, TimeUnit.MINUTES);
                return AjaxResult.success(jsonRtn);
            } else if (statusCode == 204) {
                System.out.println("success");
            } else {
                System.out.println("failed,resp code = " + statusCode+ ",return body = " + EntityUtils.toString(response.getEntity()));
                throw new IOException("request failed");
            }
        } finally {
            response.close();
            httpClient.close();
        }
        return AjaxResult.success(null);
    }

    public String getPaySign(String appid, long timestamp, String nonceStr, String prepay_id) {
        String source = appid + "\n";
        source += timestamp + "\n";
        source += nonceStr + "\n";
        source += "prepay_id=" + prepay_id + "\n";
        String paySign = "";
        try {
            paySign = SignUtil.signBySHA256WithRSA(source, WxPayConfig.privateKeyForDecrypt, "UTF-8");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return paySign;
    }

    @IgnoreAuth
    @PostMapping("/notify")
    public AjaxResult wxpayNotify(HttpServletRequest request, HttpServletResponse response) {
        try {
            String readData = HttpUtils.readData(request);
            NotificationRequest notificationRequest = new NotificationRequest.Builder()
                    .withSerialNumber(request.getHeader(WechatPayHttpHeaders.WECHAT_PAY_SERIAL))
                    .withNonce(request.getHeader(WechatPayHttpHeaders.WECHAT_PAY_NONCE))
                    .withTimestamp(request.getHeader(WechatPayHttpHeaders.WECHAT_PAY_TIMESTAMP))
                    .withSignature(request.getHeader(WechatPayHttpHeaders.WECHAT_PAY_SIGNATURE))
                    .withBody(readData)
                    .build();
            NotificationHandler handler = new NotificationHandler(verifier, WxPayConfig.apiV3Key.getBytes(StandardCharsets.UTF_8));
            // 验签和解析请求体
            Notification notification = handler.parse(notificationRequest);
            log.info("====================验签和解析请求体=================>>{}",notification.toString());
            AesUtil util = new AesUtil(WxPayConfig.apiV3Key.getBytes(StandardCharsets.UTF_8));
            Notification.Resource resource = notification.getResource();
            String ciphertext = resource.getCiphertext();
            log.info("===密文==={}",ciphertext);
            String associatedData = resource.getAssociatedData();
            String nonce = resource.getNonce();
            String plainText = util.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8),nonce.getBytes(StandardCharsets.UTF_8),ciphertext);
            log.info("===明文==={}",plainText);
            JSONObject json = JSON.parseObject(plainText);
            if("SUCCESS".equals((String)json.get("trade_state"))){
                String out_trade_no = (String)json.get("out_trade_no");
                ChatPay chatPayParam = new ChatPay();
                chatPayParam.setId(out_trade_no);
                ChatPay pay = chatPayService.queryOne(chatPayParam);
                if (null==pay) {
                    String openid = ((JSONObject)json.get("payer")).getString("openid");
                    ChatUser userInfo = chatUserService.queryByOpenId(openid);
                    if (null!=userInfo) {
                        String total_fee = ((JSONObject)json.get("amount")).getString("total");
                        log.info("支付成功");
                        Date startD = DateUtil.date();
                        String days = AppConstants.payMap.get(total_fee);
                        Calendar c = Calendar.getInstance();
                        c.setTime(startD);
                        int day = c.get(Calendar.DATE);
                        if (null!=days&&!"".equals(days)) {
                            c.set(Calendar.DATE, day + Integer.valueOf(days));
                            Date endD = c.getTime();
                            BigDecimal feeAmount = new BigDecimal(total_fee);
                            Long userId = userInfo.getUserId();
                            ChatPay chatPay = new ChatPay()
                                    .setId(out_trade_no)
                                    .setUserId(userId)
                                    .setAmount(feeAmount)
                                    .setStatus(1)
                                    .setStartDate(startD)
                                    .setEndDate(endD);
                            chatPayService.add(chatPay);
                            if (null!=userInfo.getVersion()&&!"".equals(userInfo.getVersion())) {
                                String divider = AppConstants.divideMap.get(total_fee);
                                ChatUser inviteUser = chatUserService.queryByInviteCode(userInfo.getVersion());
                                if (null!=inviteUser) {
                                    BigDecimal dividerB = new BigDecimal(divider);
                                    BigDecimal dividerAmount = feeAmount.multiply(dividerB).setScale(2, BigDecimal.ROUND_HALF_DOWN);
                                    ChatPay chatPayInvite = new ChatPay()
                                            .setId(UUIDgen.getUUID(20))
                                            .setUserId(inviteUser.getUserId())
                                            .setDivide(dividerAmount)
                                            .setDivideAmount(dividerAmount);
                                    chatPayService.add(chatPayInvite);
                                }
                            }
                            redisUtils.delete(userId+"pay");
                        }
                    }
                }
            }
            response.setStatus(200);
            return AjaxResult.success("SUCCESS");
        }catch (Exception e) {}
        return AjaxResult.success(null);
    }

    /**
     * 微信支付->扫码支付(模式二)->统一下单->微信二维码
     * @return
     */
    @GetMapping("/qrcode/{fee}")
    public AjaxResult wxpayPay(@PathVariable String fee) {
        String urlCode = null;
        // 获取订单信息
        WxpayVo vo = new WxpayVo();
        Long userId = ShiroUtils.getUserId();
        String out_trade_no = userId+"-"+UUIDgen.getUUID(10);
        vo.setOut_trade_no(out_trade_no);
        // 账号信息
        vo.setApp_id(APPID);
        vo.setMch_id(MCHID);
        vo.setKey(KEY);
        String currTime = PayToolUtil.getCurrTime();
        vo.setCurrTime(currTime);
        String strTime = currTime.substring(8, currTime.length());
        vo.setStrTime(strTime);
        String strRandom = String.valueOf(PayToolUtil.buildRandom(4));
        vo.setStrRandom(strRandom);
        String nonce_str = strTime + strRandom;
        vo.setNonce_str(nonce_str);
        vo.setSpbill_create_ip(CREATE_IP);
        vo.setNotify_url(NOTIFY_URL);
        vo.setTrade_type("NATIVE");
        vo.setTotal_fee(fee);//价格的单位为分

        SortedMap<Object,Object> packageParams = new TreeMap<Object,Object>();
        packageParams.put("appid", APPID);//公众账号ID
        packageParams.put("mch_id", MCHID);//商户号
        packageParams.put("nonce_str", nonce_str);//随机字符串
        packageParams.put("body", "卡费");  //商品描述
        packageParams.put("out_trade_no", out_trade_no);//商户订单号
        packageParams.put("total_fee", fee); //标价金额 订单总金额，单位为分
        packageParams.put("spbill_create_ip", CREATE_IP);//终端IP APP和网页支付提交用户端ip，Native支付填调用微信支付API的机器IP
        packageParams.put("notify_url", NOTIFY_URL);//通知地址 异步接收微信支付结果通知的回调地址，通知url必须为外网可访问的url，不能携带参数
        packageParams.put("trade_type", "NATIVE");//交易类型 NATIVE 扫码支付
        // 签名
        String sign = PayToolUtil.createSign("UTF-8", packageParams, KEY);
        packageParams.put("sign", sign);

        // 将请求参数转换为xml格式的string
        String requestXML = PayToolUtil.getRequestXml(packageParams);
        log.info("requestXML:{}", requestXML);

        // 调用微信支付统一下单接口
        String resXml = HttpUtil.postData(PayConfigUtil.UFDODER_URL, requestXML);
        log.info("resXml: {}", resXml);

        // 解析微信支付结果
        Map map = null;
        try {
            map = XMLUtil4jdom.doXMLParse(resXml);
            log.info("map: {}", map);
        } catch (JDOMException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 返回微信支付的二维码连接
        urlCode = (String) map.get("code_url");
        log.info("urlCode:{}", urlCode);
        int width = 300;
        int height = 300;
        QrConfig qrConfig = QrConfig.create().setWidth(width).setHeight(height);
        byte[] data = QrCodeUtil.generatePng(urlCode, qrConfig);
        String result = AppConstants.BASE64_PREFIX.concat(cn.hutool.core.codec.Base64.encode(data));
        return AjaxResult.success(result);
    }

    /**
     * 微信支付-回调
     * @param request
     * @param response
     */
    @IgnoreAuth
    @PostMapping("/notify1")
    public String wxpayNotify1(HttpServletRequest request, HttpServletResponse response) {
        //读取参数
        InputStream inputStream ;
        StringBuffer sb = null;
        try {
            sb = new StringBuffer();
            inputStream = request.getInputStream();
            String s ;
            BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
            while ((s = in.readLine()) != null){
                sb.append(s);
            }
            in.close();
            inputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

        //解析xml成map
        Map<String, String> map = new HashMap<String, String>();
        try {
            map = XMLUtil4jdom.doXMLParse(sb.toString());
        } catch (JDOMException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        //过滤空 设置 TreeMap
        SortedMap<Object,Object> packageParams = new TreeMap<Object,Object>();
        Iterator it = map.keySet().iterator();
        while (it.hasNext()) {
            String parameter = (String) it.next();
            String parameterValue = map.get(parameter);
            String v = "";
            if(null != parameterValue) {
                v = parameterValue.trim();
            }
            packageParams.put(parameter, v);
        }

        //判断签名是否正确
        if(PayToolUtil.isTenpaySign("UTF-8", packageParams, KEY)) {
            //------------------------------
            //处理业务开始
            //------------------------------
            String resXml = "";
            if("SUCCESS".equals((String)packageParams.get("result_code"))){
                // 这里是支付成功
                //执行自己的业务逻辑
                String mch_id = (String)packageParams.get("mch_id");
                String openid = (String)packageParams.get("openid");
                String is_subscribe = (String)packageParams.get("is_subscribe");
                String out_trade_no = (String)packageParams.get("out_trade_no");
                String total_fee = (String)packageParams.get("total_fee");
                //执行自己的业务逻辑
                //暂时使用最简单的业务逻辑来处理：只是将业务处理结果保存到session中
                //（根据自己的实际业务逻辑来调整，很多时候，我们会操作业务表，将返回成功的状态保留下来）
                request.getSession().setAttribute("_PAY_RESULT", "OK");
                log.info("支付成功");
                Date startD = DateUtil.date();
                String days = AppConstants.payMap.get(total_fee);
                Calendar c = Calendar.getInstance();
                c.setTime(startD);
                int day = c.get(Calendar.DATE);
                if (null!=days&&!"".equals(days)) {
                    c.set(Calendar.DATE, day + Integer.valueOf(days));
                    Date endD = c.getTime();
                    BigDecimal feeAmount = new BigDecimal(total_fee);
                    String[] paramAry = out_trade_no.split("-");
                    Long userId = Long.valueOf(paramAry[0]);
                    ChatPay chatPay = new ChatPay()
                            .setId(paramAry[1])
                            .setUserId(userId)
                            .setAmount(feeAmount)
                            .setStatus(1)
                            .setStartDate(startD)
                            .setEndDate(endD);
                    chatPayService.add(chatPay);
                    ChatUser userInfo = chatUserService.getById(userId);
                    if (null!=userInfo.getVersion()&&!"".equals(userInfo.getVersion())) {
                        String divider = AppConstants.divideMap.get(total_fee);
                        ChatUser inviteUser = chatUserService.queryByInviteCode(userInfo.getVersion());
                        BigDecimal dividerB = new BigDecimal(divider);
                        BigDecimal dividerAmount = feeAmount.multiply(dividerB).setScale(2, BigDecimal.ROUND_HALF_DOWN);
                        ChatPay chatPayInvite = new ChatPay()
                                .setId(UUIDgen.getUUID(10))
                                .setUserId(inviteUser.getUserId())
                                .setDivide(dividerAmount)
                                .setDivideAmount(dividerAmount);
                        chatPayService.add(chatPayInvite);
                    }
                }
                //通知微信.异步确认成功.必写.不然会一直通知后台.八次之后就认为交易失败了.
                resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"
                        + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
            } else {
                resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
                        + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
                return ("fail");
            }
            //------------------------------
            //处理业务完毕
            //------------------------------
            try {
                BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
                out.write(resXml.getBytes());
                out.flush();
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        } else{
            log.info("通知签名验证失败");
        }
        return ("success");
    }
}
